package com.gitee.wsl.android.ext.okio


import android.net.Uri
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.webkit.MimeTypeMap
import com.gitee.wsl.android.ext.store.contentResolver
import com.google.modernstorage.storage.MetadataExtras
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonReader
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import okio.*
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.Locale


object OkioExt {


    fun sink(inputStream: InputStream, out: OutputStream){

    }

    fun listPhysicalDirectory(dir: Path, throwOnFailure: Boolean): List<Path>? {
        val file = dir.toFile()
        val entries = file.list() ?: if (throwOnFailure) {
            if (!file.exists()) throw FileNotFoundException("no such file: $dir")
            throw IOException("failed to list $dir")
        } else {
            return null
        }
        val result = entries.mapTo(mutableListOf()) { dir / it }
        result.sort()
        return result
    }

     fun listDocumentProvider(dir: Path, throwOnFailure: Boolean): List<Path>? {
        // TODO: Verify path is a directory
        val rootUri = dir.toUri()
        val documentId = DocumentsContract.getDocumentId(rootUri)
        val treeUri = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, documentId)

        val cursor = contentResolver.query(
            treeUri,
            arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID),
            null,
            null,
            null,
            null
        )?: if (throwOnFailure) {
                throw IOException("failed to list $dir")
            } else {
                return null
            }

        val result = mutableListOf<Path>()

        cursor.use { cursor ->
            while (cursor.moveToNext()) {
                result.add(DocumentsContract.buildDocumentUriUsingTree(rootUri, documentId).toOkioPath())
            }
        }

        return result
    }

     fun fetchMetadataFromPhysicalFile(path: Path): FileMetadata? {
        val file = path.toFile()
        val isRegularFile = file.isFile
        val isDirectory = file.isDirectory
        val lastModifiedAtMillis = file.lastModified()
        val size = file.length()

        if (!isRegularFile &&
            !isDirectory &&
            lastModifiedAtMillis == 0L &&
            size == 0L &&
            !file.exists()
        ) {
            return null
        }

        val fileExtension: String = MimeTypeMap.getFileExtensionFromUrl(file.toString())
        val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.lowercase(
            Locale.getDefault()))

        val extras = mutableMapOf(
            Path::class to path,
            MetadataExtras.DisplayName::class to MetadataExtras.DisplayName(file.name),
            MetadataExtras.FilePath::class to MetadataExtras.FilePath(file.absolutePath),
        )

        if (mimeType != null) extras[MetadataExtras.MimeType::class] = MetadataExtras.MimeType(mimeType)

        return FileMetadata(
            isRegularFile = isRegularFile,
            isDirectory = isDirectory,
            symlinkTarget = null,
            size = size,
            createdAtMillis = null,
            lastModifiedAtMillis = lastModifiedAtMillis,
            lastAccessedAtMillis = null,
            extras = extras
        )
    }

     fun fetchMetadataFromMediaStore(path: Path, uri: Uri): FileMetadata? {
        if (uri.pathSegments.firstOrNull().isNullOrBlank()) {
            return null
        }

        val isPhotoPickerUri = uri.pathSegments.firstOrNull() == "picker"

        val projection = if (isPhotoPickerUri) {
            arrayOf(
                MediaStore.MediaColumns.DATE_TAKEN,
                MediaStore.MediaColumns.DISPLAY_NAME,
                MediaStore.MediaColumns.MIME_TYPE,
                MediaStore.MediaColumns.SIZE,
                MediaStore.MediaColumns.DATA,
            )
        } else {
            arrayOf(
                MediaStore.MediaColumns.DATE_ADDED,
                MediaStore.MediaColumns.DATE_MODIFIED,
                MediaStore.MediaColumns.DISPLAY_NAME,
                MediaStore.MediaColumns.MIME_TYPE,
                MediaStore.MediaColumns.SIZE,
                MediaStore.MediaColumns.DATA,
            )
        }

        val cursor = contentResolver.query(
            uri,
            projection,
            null,
            null,
            null
        ) ?: return null

        cursor.use { cursor ->
            if (!cursor.moveToNext()) {
                return null
            }

            val createdTime: Long
            var lastModifiedTime: Long? = null

            if (isPhotoPickerUri) {
                createdTime = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_TAKEN))
            } else {
                createdTime = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED))
                lastModifiedTime = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED))
            }

            val displayName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME))
            val mimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE))
            val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE))
            val filePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA))

            return FileMetadata(
                isRegularFile = true,
                isDirectory = false,
                symlinkTarget = null,
                size = size,
                createdAtMillis = createdTime,
                lastModifiedAtMillis = lastModifiedTime,
                lastAccessedAtMillis = null,
                extras = mapOf(
                    Path::class to path,
                    Uri::class to uri,
                    MetadataExtras.DisplayName::class to MetadataExtras.DisplayName(displayName),
                    MetadataExtras.MimeType::class to MetadataExtras.MimeType(mimeType),
                    MetadataExtras.FilePath::class to MetadataExtras.FilePath(filePath),
                )
            )
        }
    }

     fun fetchMetadataFromDocumentProvider(path: Path, uri: Uri): FileMetadata? {
        val cursor = contentResolver.query(
            uri,
            arrayOf(
                DocumentsContract.Document.COLUMN_LAST_MODIFIED,
                DocumentsContract.Document.COLUMN_DISPLAY_NAME,
                DocumentsContract.Document.COLUMN_MIME_TYPE,
                DocumentsContract.Document.COLUMN_SIZE
            ),
            null,
            null,
            null
        ) ?: return null

        cursor.use { cursor ->
            if (!cursor.moveToNext()) {
                return null
            }

            // DocumentsContract.Document.COLUMN_LAST_MODIFIED
            val lastModifiedTime = cursor.getLong(0)
            // DocumentsContract.Document.COLUMN_DISPLAY_NAME
            val displayName = cursor.getString(1)
            // DocumentsContract.Document.COLUMN_MIME_TYPE
            val mimeType = cursor.getString(2)
            // DocumentsContract.Document.COLUMN_SIZE
            val size = cursor.getLong(3)

            val isFolder = mimeType == DocumentsContract.Document.MIME_TYPE_DIR ||
                    mimeType == DocumentsContract.Root.MIME_TYPE_ITEM

            return FileMetadata(
                isRegularFile = !isFolder,
                isDirectory = isFolder,
                symlinkTarget = null,
                size = size,
                createdAtMillis = null,
                lastModifiedAtMillis = lastModifiedTime,
                lastAccessedAtMillis = null,
                extras = mapOf(
                    Path::class to path,
                    Uri::class to uri,
                    MetadataExtras.DisplayName::class to MetadataExtras.DisplayName(displayName),
                    MetadataExtras.MimeType::class to MetadataExtras.MimeType(mimeType),
                )
            )
        }
    }
}



inline fun <reified T> Moshi.fromJson(json: String): T =
    fromJson(Buffer().writeUtf8(json))

inline fun <reified T> Moshi.fromJson(source: BufferedSource): T =
    fromJson(JsonReader.of(source))

@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> Moshi.fromJson(reader: JsonReader): T =
    adapter<T>().fromJson(reader) ?: throw JsonDataException()

@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> Moshi.toJson(value: T): String =
    adapter<T>().toJson(value)

inline fun <reified T> JsonAdapter<T>.fromJson(json: ByteString): T? {
    return fromJson(json.utf8())
}

inline fun <reified T> JsonAdapter<T>.toByteString(value: T): ByteString {
    val buffer = Buffer()
    buffer.use {
        toJson(it, value)
    }
    return buffer.readByteString()
}

